package PoeHelper;

use strict;
use warnings;
use Smart::Comments;

use POE;
use Devel::Declare;

our %method = (
    ':common' => [qw(
        yield post call delay alias_set
    )],
    ':timer_name_based' => [qw(
        delay delay_add
        alarm alarm_add
    )],
    ':timer_id_based' => [qw(
        alarm_set alarm_adjust alarm_remove alarm_remove_all
        delay_set delay_adjust
    )],
    ':session_id' => [qw(
        alias_set
        alias_remove alias_resolve alias_list
        ID_id_to_session ID_session_to_id
    )],
    ':io_watcher' => [qw(
        select_read
        select_write
        select_expedite
        select_pause_read
        select_resume_read
        select_pause_write
        select_resume_write
        select
    )],
    ':signal_watcher' => [qw(
        sig sig_child sig_handled signal signal_ui_destroy
    )],
    ':etc' => [qw(
        state
        refcount_increment refcount_decrement
        get_active_session
        get_active_event
        get_event_count
        get_next_event_time
    )],
);

$method{':all'} = [ map { @{ $method{$_} } } keys %method ];

$method{':timer'} = [
    @{ $method{':timer_name_based'} },
    @{ $method{':timer_id_based'} },
];

sub import {
    my $class  = shift;
    my $caller = caller;
    my @methods = @_ ? @_ : qw(:common);

    Devel::Declare->setup_for( $caller, {
        event   => { const => \&parser },
        service => { const => \&parser },
    });

    {
        no strict 'refs';
        *{ $caller . '::event' }        = sub (&) { };
        *{ $caller . '::service' }      = sub (&) { };
        *{ $caller . "::spawnSession" } = \&spawnSession;
    }

    return if $methods[0] eq ':none';

    my %seen;
    @methods = grep {
        !$seen{$_}++
    } map {
        @{ $method{$_} || [] }
    } @methods;

    foreach my $method ( @methods ){
        no strict 'refs';
        *{ $caller . "::$method" } = sub { $POE::Kernel::{$method}( $POE::Kernel::poe_kernel, @_ ) };
    }

    return;
}

our ( $Declarator, $Offset );

sub parser {
    local ( $Declarator, $Offset ) = @_;

    # 跳过关键字 event
    skip_declarator();

    # 拿到 event 的名字
    my $name = strip_name();
    if ( not defined $name ){
        my $linestr = Devel::Declare::get_linestr();
        chomp $linestr;
        die "event or service 后面缺少事件名称 near $linestr。\n";
    }

    # 拿到原型
    my $proto = strip_proto();

    my $inject = "}; sub $name {";
    $inject .= make_proto_unwrap($proto);
    inject_if_block($inject);
}

sub skip_declarator {
    $Offset += Devel::Declare::toke_move_past_token($Offset);
}

sub strip_name {
    skipspace();

    my $len = Devel::Declare::toke_scan_word( $Offset, 1 );
    if ( $len ) {
        my $linestr = Devel::Declare::get_linestr();
        my $name = substr( $linestr, $Offset, $len );
        substr( $linestr, $Offset, $len ) = '';
        Devel::Declare::set_linestr( $linestr );
        return $name;
    }

    return;
}

sub skipspace {
    $Offset += Devel::Declare::toke_skipspace( $Offset );
}

sub strip_proto {
    skipspace;

    my $linestr = Devel::Declare::get_linestr();
    if ( substr( $linestr, $Offset, 1 ) eq '(' ) {
        my $length = Devel::Declare::toke_scan_str($Offset);
        my $proto  = Devel::Declare::get_lex_stuff();
        Devel::Declare::clear_lex_stuff();
        $linestr = Devel::Declare::get_linestr();
        substr( $linestr, $Offset, $length ) = '';
        Devel::Declare::set_linestr($linestr);
        return $proto;
    }
    return;
}

sub make_proto_unwrap {
    my $proto = shift;
    my $inject = 'my @POE = splice @_, 0, ARG0;';
    $inject .= 'my ($kernel, $sender, $session, $heap) = @POE[ KERNEL, SENDER, SESSION, HEAP ];';
    $inject .= 'my $cfg = $heap->{cfg};';
    $inject .= 'my $box = $heap->{box};';
    if ( $proto ) {
        $inject .= "my ($proto) = \@_;";
    }
    return $inject;
}

sub inject_if_block {
    my $inject = shift;
    skipspace;
    my $linestr = Devel::Declare::get_linestr;
    if ( substr( $linestr, $Offset, 1 ) eq '{' ) {
        substr( $linestr, $Offset + 1, 0 ) = $inject;
        Devel::Declare::set_linestr($linestr);
    }
}

sub spawnSession {
    my @event = @_;

    my $caller = caller;

    my $options = {
        map { ref $_ eq 'HASH' ? %$_ : () } @event
    };

    @event = grep { not ref $_ } @event;

    my $cfg = delete $options->{cfg} || {};
    my $box = delete $options->{box} || {};
    my $heap = delete $options->{heap} || {};
    $heap->{cfg} = $cfg;
    $heap->{box} = $box;

    foreach my $e ( qw(_stop _parent _child _default) ){
        push @event, $e if $caller->can($e);
    }

    create POE::Session(
        package_states => [
            $caller => [ '_start', @event ],
        ],
        heap => $heap,
        %$options,
    );
};

1;
